package top.letscode.adclicker.annotation;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 加载相关的注解类
 */
public class AnnotationLoader {

  private static final String CLASS_SUFFIX = ".class";
  public static final String BASE_PACKAGE = "top.letscode.adclicker";

  // cache loaded annotations
  @SuppressWarnings("rawtypes")
  private static Map<Class, List<Class>> CACHED_MAP = new ConcurrentHashMap<Class, List<Class>>();

  private static ClassLoader getClassLoader() {
    return AnnotationLoader.class.getClassLoader();
  }

  private static boolean isClassFile(String fileName) {
    if (null == fileName) {
      return false;
    }

    return fileName.endsWith(CLASS_SUFFIX);
  }

  private static boolean isJarFile(String filePath) {
    if (null == filePath) {
      return false;
    }

    return filePath.contains(".jar!");
  }

  private static String getJarPath(String filePath) {
    return filePath.substring(filePath.indexOf("file:") + ("file:" + File.separator).length(),
        filePath.indexOf("!"));
  }

  @SuppressWarnings("rawtypes")
  public static List<Class> load(Class classz, String basePackage) {
    if (CACHED_MAP.containsKey(classz)) {
      return CACHED_MAP.get(classz);
    }

    File classBaseDir = new File(AnnotationLoader.class
        .getResource("/" + basePackage.replaceAll("(\\w)\\.", "$1/")).getFile());
    List<Class> classList = new ArrayList<Class>();

    if (classBaseDir.isDirectory()) {// 从directory中加载
      loadClassFromDir(classz, classBaseDir, classList, basePackage);
    } else if (!classBaseDir.isFile() && isJarFile(classBaseDir.getAbsolutePath())) {// 从jar中加载classes
      loadClassFromJar(classz, classBaseDir, classList);
    }

    CACHED_MAP.put(classz, classList);

    return classList;
  }

  @SuppressWarnings({"rawtypes", "unchecked"})
  private static void loadClassFromJar(Class annoClassz, File classBaseDir, List<Class> classList) {
    String jarPath = getJarPath(classBaseDir.getAbsolutePath());
    JarInputStream jarStream = null;

    try {
      jarStream = new JarInputStream(new FileInputStream(jarPath));
      JarEntry jarEntry = null;

      while ((jarEntry = jarStream.getNextJarEntry()) != null) {
        if (isClassFile(jarEntry.getName())) {
          Class classz = getClassLoader()
              .loadClass(jarEntry.getName().replace("/", ".").replace(CLASS_SUFFIX, ""));
          Annotation annotation = classz.getAnnotation(annoClassz);
          if (annotation != null) {
            classList.add(classz);
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (jarStream != null) {
        try {
          jarStream.closeEntry();
          jarStream.close();
        } catch (IOException e) {// ignore

        }
      }
    }
  }

  /**
   * 从dir中加载classes
   * 
   * @param annoClassz 要加载的注解类
   * @param directory 加载的目录
   * @param classList 当前已加载的Class列表
   * @param basePackage 类所在的包名
   */
  @SuppressWarnings("rawtypes")
  private synchronized static void loadClassFromDir(Class annoClassz, File directory,
      List<Class> classList, String basePackage) {
    if (CACHED_MAP.containsKey(annoClassz)) {
      return;
    }

    for (File file : directory.listFiles()) {
      if (file.isDirectory()) {
        loadClassFromDir(annoClassz, file, classList, basePackage + "." + file.getName());
        continue;
      }

      if (!isClassFile(file.getName())) {
        continue;
      }

      try {
        Class classz = getClassLoader()
            .loadClass(basePackage + "." + file.getName().replace(CLASS_SUFFIX, ""));
        Annotation[] annotations = classz.getAnnotations();
        if (annotations == null || annotations.length == 0) {
          continue;
        }

        for (Annotation annotation : annotations) {
          if (annotation.annotationType() == annoClassz) {
            classList.add(classz);
            break;
          }
        }
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    }
  }
}
